package ch.trackedbean.server;

import java.io.*;
import java.rmi.*;
import java.rmi.registry.*;
import java.rmi.server.*;
import java.util.*;
import ch.simpleel.util.*;
import ch.simpleel.util.ListMergeHelper.IMergeHandler;
import ch.trackedbean.copier.*;
import ch.trackedbean.server.data.dom.*;
import ch.trackedbean.server.data.to.*;
import ch.trackedbean.validator.*;

/**
 * Main class of the server part.<br>
 * You must start the <code>rmiregistry</code> before you start this server implementation.
 * 
 * @author hautle
 */
public class ServerImp implements Server {
    /** Map holding the data. */
    private Map<Class<?>, Map<Integer, Dom>> data = new HashMap<Class<?>, Map<Integer, Dom>>();

    /** Counter for address keys. */
    private static int cnt = 1;

    /**
     * Default constructor.
     */
    public ServerImp() {
        createData();
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public <T> T fetchLazy(Class<? extends TO> type, Integer key, String attribute) throws RemoteException {
        final IBeanMapper mapper = BeanMapperManager.getMapper(type);
        final Dom src = get(mapper.getMappedBaseType(), key);
        return (T) mapper.getMappingHolder(attribute).getSrcValue(src);
    }

    /**
     * {@inheritDoc}
     */
    public <T extends Dom> List<T> getDomObjects(Class<T> type) throws RemoteException {
        final List<T> res = new ArrayList<T>();
        res.addAll(getMap(type).values());
        return res;
    }

    /**
     * {@inheritDoc}
     */
    public PersonTO getPerson(Integer key) throws RemoteException {
        return BeanMapperManager.copySrc2Dst(get(PersonDom.class, key), new PersonTO());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<PersonShort> getPersons() throws RemoteException {
        final List<PersonShort> res = new ArrayList<PersonShort>();
        for (PersonDom p : getMap(PersonDom.class).values())
            res.add(BeanMapperManager.copySrc2Dst(p, new PersonShort()));
        return res;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void savePerson(PersonTO person) throws RemoteException, ValidationException {
        ValidatorManager.ensureCorrectness(person);
        final PersonDom p = get(PersonDom.class, person.getKey());
        final List<AddressDom> addr = p.getAddresses();
        BeanMapperManager.copyDst2Src(person, p);
        final byte[] pic = person.getPicture();
        if (pic != null)
            p.setPicture(pic);
        final CrossElementComparator<AddressDom> cmp = new CrossElementComparator<AddressDom>(AddressDom.class, AddressDom.ATTR_KEY);
        final ListMergeHelper<AddressDom> adresses = new ListMergeHelper<AddressDom>(cmp);
        cmp.addAccessors(AddressTO.class, AddressTO.ATTR_KEY);
        adresses.addElements(p.getAddresses());
        adresses.merge(person.getAddresses(), new IMergeHandler<AddressDom, AddressTO>() {
            @Override
            public void added(AddressTO el) {
                final AddressDom adr = new AddressDom();
                addr.add(BeanMapperManager.copyDst2Src(el, adr));
                adr.setKey(new Integer(cnt++));
            }

            @Override
            public void merged(AddressTO src, AddressDom dst) {
                BeanMapperManager.copyDst2Src(src, dst);
            }

            @Override
            public void removed(AddressDom el) {
                addr.remove(el);
            }
        });
    }

    /**
     * Creates the data for {@link #data}.
     */
    private void createData() {
        // countries
        addEntry(new CountryDom(new Integer(1), "Switzerland"));
        addEntry(new CountryDom(new Integer(2), "Germany"));
        addEntry(new CountryDom(new Integer(3), "England"));
        addEntry(new CountryDom(new Integer(4), "Italy"));
        // citys
        addEntry(new PlaceDom(new Integer(1), 3000, "Bern"));
        addEntry(new PlaceDom(new Integer(2), 1700, "Fribourg"));
        addEntry(new PlaceDom(new Integer(3), 8080, "Zürich"));
        // persons
        addEntry(createPerson(1, "Lopez", "Juan", 30, "Eiger", "5", 1, 1));
        addEntry(createPerson(2, "Rodriguez", "Jose", 25, "Marktgasse", "13", 3, 1));
    }

    /**
     * Creates a {@link PersonDom}.
     * 
     * @param key The object key
     * @param name The name
     * @param firstName The first name
     * @param age The age
     * @param street The street
     * @param streetNr The street number
     * @param place The key of the {@link PlaceDom} to link
     * @param country The key of the {@link CountryDom} to link
     * @return A person DOM
     */
    private PersonDom createPerson(int key, String name, String firstName, int age, String street, String streetNr, int place, int country) {
        final PersonDom p = new PersonDom(new Integer(key), name, firstName, 30);
        final List<AddressDom> adds = new ArrayList<AddressDom>();
        adds.add(new AddressDom(new Integer(cnt++), street, streetNr, get(PlaceDom.class, new Integer(place)), get(CountryDom.class, new Integer(country))));
        p.setAddresses(adds);
        p.setPicture((name + " " + firstName).getBytes());
        return p;
    }

    /**
     * Looksup the DOM with the given key.
     * 
     * @param <T> The type
     * @param type The desired type
     * @param key The key of the DOM
     * @return The DOM with the given key
     */
    private <T extends Dom> T get(Class<T> type, Integer key) {
        return getMap(type).get(key);
    }

    /**
     * Adds the given DOM to the {@link #data} map.
     * 
     * @param <T> The dom type
     * @param d The DOM
     */
    @SuppressWarnings("unchecked")
    private <T extends Dom> void addEntry(T d) {
        Map<Integer, T> map = (Map<Integer, T>) getMap(d.getClass());
        map.put(d.getKey(), d);
    }

    /**
     * Returns the corresponding Map for the given type.
     * 
     * @param <T> The dom type
     * @param type The DOM type
     * @return The corresponding map
     */
    @SuppressWarnings("unchecked")
    private <T extends Dom> Map<Integer, T> getMap(Class<T> type) {
        Map<Integer, Dom> map = data.get(type);
        if (map == null)
            data.put(type, map = new HashMap<Integer, Dom>());
        return (Map<Integer, T>) map;
    }

    /**
     * Starts the server
     * 
     * @param args Not used
     */
    public static void main(String[] args) {

        try {
            // we build the classpath for the RMI-Registry by parsing the classpath of this JVM.
            final StringBuilder str = new StringBuilder();
            for (String s : System.getProperty("java.class.path").split(File.pathSeparator))
                str.append(new File(s).toURI().toURL().toExternalForm()).append(' ');
            System.setProperty("java.rmi.server.codebase", str.toString());
            //
            final Registry registry = LocateRegistry.createRegistry(1099);
            final Remote srv = UnicastRemoteObject.exportObject(new ServerImp(), 0);
            registry.rebind(Server.NAME, srv);
            System.out.println("Server started...");
        } catch (Exception e) {
            System.err.println("Binding failed:");
            e.printStackTrace();
        }
    }
}
